
#include "device.h"

#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include "dump.h"

#define UART_NONBLOCK   0x00000800
#define UART_NOCTTY     0x00000100
#define UART_RDONLY     0x00000000
#define UART_WRONLY     0x00000001
#define UART_RDWR       0x00000002


#if defined(ANDROID)
#include "android/log.h"
#include "utils/Log.h"
#ifdef LOG_TAG
#undef LOG_TAG
#endif
#define LOG_TAG "SerialControl"
#else
#include <stdio.h>
#define ALOGD printf
#endif


namespace serial {

static struct {
    int     nspeed;
    speed_t sspeed;
} g_speed_table[] = {
    {  0,       B0      },
    { 50,       B50     },
    { 75,       B75     },
    { 110,      B110    },
    { 134,      B134    },
    { 150 ,     B150    },
    { 200,      B200    },
    { 300,      B300    },
    { 600,      B600    },
    { 1200,     B1200   },
    { 1800,     B1800   },
    { 2400,     B2400   },
    { 4800,     B4800   },
    { 9600,     B9600   },
    { 19200,    B19200  },
    { 38400,    B38400  },
    { 57600,    B57600  },
    { 115200,   B115200 },
    { 230400,   B230400 },
};

Device::Device(const char * node)
    : mFd(-1)
    , mNode(NULL)
    , repeat_log_times(0)
{
    if (node != NULL)
        mNode = strdup(node);
    pipefd[0] = -1;
    pipefd[1] = -1;
}

Device::~Device()
{
    reset();
    if (mNode != NULL) {
        free(mNode);
        mNode = NULL;
    }
}

void Device::reset()
{
    if (mFd >= 0)
        close(mFd);
    mFd = -1;
}

int Device::findBitrate(int bitrate)
{
    int count = sizeof(g_speed_table) / sizeof(g_speed_table[0]);
    for (int i = 0; i < count; i++) {
        if (g_speed_table[i].nspeed == bitrate)
            return g_speed_table[i].sspeed;
    }
    return -1;
}

bool Device::init(const char * node, int bitrate, int databit, Device::PARITY parity, int stopbit, Device::FLOWCONTROL flowcontrol)
{
    if (node == NULL)
        node = mNode;

    if (node == NULL) {
        ALOGD("node == NULL");
        return false;
    }

    repeat_log_times++;

    if (repeat_log_times < 20 || (repeat_log_times % 10) == 0)
        ALOGD("init: %s, %d, %d, %d, %d, %d\n", node, bitrate, databit, (int)parity, stopbit, (int)flowcontrol);

    // for test...
    if (strstr(node, "/dev/") == NULL) {
        mFd = open(node, O_RDONLY);
        if(mFd < 0) {
            ALOGD("open test file [%s] failed: %s", node, strerror(errno));
            return false;
        }
        return true;
    }

    // check params.
    speed_t speed = findBitrate(bitrate);
    if (speed == (speed_t)(-1)) {
        ALOGD("error bitrate: %d", bitrate);
        return false;
    }

    if (databit < 5 || databit > 8) {
        ALOGD("invalid databit: %d", databit);
        return false;
    }

    // init.
    mFd = open(node, O_NOCTTY | O_RDWR);
    if(mFd < 0) {
        if (repeat_log_times < 20 || (repeat_log_times % 10) == 0)
            ALOGD("open uart node[%s] with bitrate %d failed: %s", node, bitrate, strerror(errno));
        return false;
    }
    repeat_log_times = 0;

    struct termios io;
    memset(&io, 0, sizeof(io));
    cfmakeraw(&io);

    // set bitrate.
    cfsetispeed(&io, speed);
    cfsetospeed(&io, speed);

    if (tcsetattr(mFd, TCSANOW, &io) != 0) {
        ALOGD("tcsetattr.TCSANOW failed: %s", strerror(errno));
        reset();
        return false;
    }

    tcflush(mFd, TCIOFLUSH);

    if (tcgetattr(mFd, &io) != 0) {
        ALOGD("tcgetattr failed: %s", strerror(errno));
        reset();
        return false;
    }

    // set databit.
    io.c_cflag &= ~CSIZE;
    if (databit == 5) {
        io.c_cflag |= CS5;
    } else if (databit == 6) {
        io.c_cflag |= CS6;
    } else if (databit == 7) {
        io.c_cflag |= CS7;
    } else {
        io.c_cflag |= CS8;
    }

    // set parity
    if (parity == EVEN) {
        io.c_iflag |= (INPCK | ISTRIP);
        io.c_cflag |= PARENB;
        io.c_cflag &= ~PARODD;
    } else if (parity == ODD) {
        io.c_iflag |= (INPCK | ISTRIP);
        io.c_cflag |= PARENB;
        io.c_cflag |= PARODD;
    } else {
        io.c_cflag &= ~PARENB;
        io.c_iflag &= ~INPCK;
        // io.i_cflag &= ~ISTRIP;
    }

    // set stopbit
    if (stopbit == 2) {
        io.c_cflag |= CSTOPB;
    } else {
        io.c_cflag &= ~CSTOPB;
    }

    io.c_oflag &= ~OPOST;
    io.c_lflag &= ~(ICANON | ECHO | ECHOE |ISIG);
    io.c_cc[VTIME] = 0;
    io.c_cc[VMIN] = 1;
    io.c_cflag |= (CLOCAL | CREAD);
    io.c_iflag &= ~(BRKINT | ICRNL | IXON);

    if (flowcontrol == HARDWARE_FLOWCTRL) {
        io.c_cflag |= CRTSCTS;
    } else if (flowcontrol == SOFTWARE_FLOWCTRL) {
        io.c_cflag |= (IXON | IXOFF | IXANY);
    } else {
        io.c_cflag &= ~CRTSCTS;
        io.c_iflag &= ~(IXON | IXOFF | IXANY);
    }

    tcsetattr(mFd, TCSANOW, &io);
    tcflush(mFd, TCIOFLUSH);

    initpipe();

    onDeviceInitialized();
    return true;
}

void Device::initpipe()
{
    if (pipefd[0] >= 0)
        return;
    if (pipe(pipefd) != 0) {
        pipefd[0] = -1;
        pipefd[1] = -1;
        ALOGD("create pipe failed. %s", strerror(errno));
    }
}

int Device::read()
{
    if (mFd < 0) {
        onRead(NULL, -1);
        return -1;
    }

    char buffer[1024] = {0};
    int ret = ::read(mFd, buffer, sizeof(buffer));
    onRead(buffer, ret);
    if (ret <= 0) {
        onReadError(errno);
        reset();
    }
    return ret;
}

int Device::writefd()
{
    return pipefd[0];
}

int Device::write(const void* buf, int len)
{
    if (buf == NULL || len <= 0) {
        ALOGD("Deivce::write failed because buf is null or len(%d) < 0", len);
        return -1;
    }
    int ret = ::write(pipefd[1], buf, len);
    if (ret < 0) {
        onWriteError(errno);
    }
    return ret;
}

int Device::doWriteDevice()
{
    if (mFd < 0)
        return -1;

    if (pipefd[0] < 0)
        return -1;

    char buffer[1024] = {0};
    int ret = ::read(pipefd[0], buffer, sizeof(buffer));
    ALOGD("write %d bytes data to device (%s): ", ret, mNode);
    tools::dump(buffer, ret);
    return ::write(mFd, buffer, ret);
}

} //namespace serial


